/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is lutsko.com code.
 *
 * The Initial Developer of the Original Code is  James Lutsko 
 * (jim@lutsko.com http://www.lutsko.com).
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

package autoschedule;



import java.io.Serializable;
import java.util.Date;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.Collections;
import java.util.Vector;

import autoteam.Local;

public class Day implements Serializable, Comparable
{
    private Date date;
    Time start;
    Time end;
    
    LinkedList open;
    LinkedList games;

    static final double MIN_CAP  = 0.1;
    static final int MIN_DURATION = 10; 

    static Calendar cal = Calendar.getInstance();

    public Day()
    {
	date = new Date(); 
	zeroDate();
	start = new Time(1200);
	end = new Time(1200);
    }
    
    public void unScheduleAll()
    {
	Vector tmp = new Vector();
	for(Iterator I = games.iterator();I.hasNext();)
	    {
		TimeBlock tb = (TimeBlock) I.next();
		if(tb.g == null) continue;
		Game g = tb.g;
		if(g.facility != null)
		    tmp.add(g);
	    }
	for(Iterator I1 = tmp.iterator();I1.hasNext();)
	    {
		Game g = (Game) I1.next();
		g.unSchedule();
	    }		
    }

    public void unScheduleBefore(Integer val)
    {
	Vector tmp = new Vector();
	for(Iterator I = games.iterator();I.hasNext();)
	    {
		TimeBlock tb = (TimeBlock) I.next();
		if(tb.a.time < val.intValue())
		    {
			if(tb.g == null) continue;
			Game g = tb.g;
			if(g.facility != null)
			    tmp.add(g);
		    }
	    }
	for(Iterator I1 = tmp.iterator();I1.hasNext();)
	    {
		Game g = (Game) I1.next();
		g.unSchedule();
	    }		
    }

    public void unScheduleAfter(Integer val)
    {
	Vector tmp = new Vector();
	for(Iterator I = games.iterator();I.hasNext();)
	    {
		TimeBlock tb = (TimeBlock) I.next();
		if(tb.b.time > val.intValue())
		    {
			if(tb.g == null) continue;
			Game g = tb.g;
			if(g.facility != null)
			    tmp.add(g);
		    }
	    }
	for(Iterator I1 = tmp.iterator();I1.hasNext();)
	    {
		Game g = (Game) I1.next();
		g.unSchedule();
	    }		
    }
    
    public void addOpenBlock(TimeBlock tb)
    {
	while(open.remove(null)){;}

	Iterator It = open.iterator();
	int index = 0;
	while(It.hasNext())
	    {	
		TimeBlock tb1 = (TimeBlock) It.next();
		if(tb.a.time > tb1.b.time) index++;
	    }
	open.add(index,tb);

	doConsolidate();

    }

    public void doConsolidate()
    {
	// Order the time blocks
	// first criterion is start times
	// second is end times
	Collections.sort(open);

	// consolidate: this does not cover the case of partial overlaps of multi-game fields
	// i.e. tb1 = (0800,1000,0.5) tb2 = (0900,1000,0.5)



	// Merge consecutive && coincident blocks with same capacity
	boolean changeFlag;
	do {
	    changeFlag = false;
	    for(Iterator I1 = open.iterator();I1.hasNext();)
		{
		    TimeBlock tb1 = (TimeBlock) I1.next();
		    for(Iterator I2 = open.iterator();I2.hasNext();)
			{
			    TimeBlock tb2 = (TimeBlock) I2.next();
			    
			    if(tb1 == tb2) continue;
			    
			    if(tb2 != null && tb2.b.time >= 0)
				{
				    if(tb1.a.time == tb2.a.time) // coincident blocks
					{
					    if(tb1.b.time == tb2.b.time) 
						{
						    tb1.capacity += tb2.capacity;
						    tb2.a.time = tb2.b.time = -1;
						    changeFlag = true;
						}
					} else if(tb1.b.time == tb2.a.time) { // consecutive blocks
					    if(tb1.capacity == tb2.capacity)
						{
						    tb1.b.time = tb2.b.time;
						    tb2.a.time = tb2.b.time = -1;
						    changeFlag = true;
						}
					}
				}
			}
		}
	} while(changeFlag);

	for(Iterator I3 = open.iterator();I3.hasNext();)
	    {
		TimeBlock tb3 = (TimeBlock) I3.next();
		if(tb3.b.time < 0) 
		    I3.remove();
	    }

	for(Iterator I4 = open.iterator();I4.hasNext();)
	    {
		TimeBlock tb4 = (TimeBlock) I4.next();
		if(tb4.b.time <= tb4.a.time) 
		    I4.remove();
	    }

    }

    private void zeroDate()
    {
	cal.setTime(date);
	cal.set(cal.HOUR,10);
	cal.set(cal.MINUTE,0);
	cal.set(cal.SECOND,0);
	cal.set(cal.MILLISECOND,0);
	date = cal.getTime();
    }

    public Day(Day d)
    {
	date = (Date) d.date.clone(); 	
	start = new Time(d.start);
	end = new Time(d.end);
    }

    public Day(Date d, Time s, Time e)
    {
	date = (d == null ? new Date() : d);
	zeroDate();
    	start = s;
	end = e; 
    }
    public Day(Date d, TimeBlock tb)
    {
	date = (d == null ? new Date() : d);
	zeroDate();
	if(open == null) open = new LinkedList();
	open.addFirst(tb);
    }

    public Time getStart(){return start;}
    public Time getEnd(){return end;}
    public Date getDate(){ return date;}

    public void setStart(Integer var)
    { 
	int newStart = var.intValue();
	if(start.time != newStart) 
	    {
		if(open != null)
		    {
			if(start.time > newStart)
			    {
				open.add(new TimeBlock(new Time(newStart), new Time(start.time)));
			    } else {
				Iterator It = open.iterator();
				while(It.hasNext())
				    {	
					TimeBlock tb = (TimeBlock) It.next();
					if(tb.b.time < newStart) open.remove(tb); 
					else if(tb.a.time < newStart) tb.a.time = newStart;
				    }
			    }
			doConsolidate();
		    }
		start = new Time(newStart);
	    }
    }
    public void setEnd(Integer var)
    { 
	int newEnd = var.intValue();
	if(end.time != newEnd) 
	    {
		if(open != null)
		    {
			if(end.time < newEnd)
			    {
				open.add(new TimeBlock(new Time(end.time),new Time(newEnd)));
			    } else {
				Iterator It = open.iterator();
				while(It.hasNext())
				    {	
					TimeBlock tb = (TimeBlock) It.next();
					if(tb.a.time  > newEnd) It.remove(); 
					else if(tb.b.time > newEnd) tb.b.time = newEnd;
				    }
			    }
			doConsolidate();
		    }
		end = new Time(newEnd);
	    }
    }
    public void setDate(Date var){ date = var;	zeroDate();}

    boolean before(Date _date)
    {
	cal.setTime(date);
	int y  = cal.get(cal.YEAR);
	int d = cal.get(cal.DAY_OF_YEAR);

	cal.setTime(_date);
	if(y < cal.get(cal.YEAR)) return true;
	if(d <  cal.get(cal.DAY_OF_YEAR)) return true;

	return false;
    }

    boolean before(Day _day)
    {
	cal.setTime(date);
	int y  = cal.get(cal.YEAR);
	int d = cal.get(cal.DAY_OF_YEAR);

	cal.setTime(_day.date);
	if(y < cal.get(cal.YEAR)) return true;
	if(d <  cal.get(cal.DAY_OF_YEAR)) return true;

	return false;
    }

    public int getDayOfWeek()
    {
	cal.setTime(date);
	return cal.get(cal.DAY_OF_WEEK)-1; // Sun = 0, Mon = 1, etc.
    }

    public int duration(){return end.minus(start);}

    public void incrementStart(int minutes){start = start.increment(minutes);}

    void initialize()
    {
	open = new LinkedList();
	games = new LinkedList();
	open.addFirst(new TimeBlock(start,end));
    }

    // duration in minutes
    boolean hasTimeSlot(Integer simulGames,int duration)
    {
	if(open == null) initialize();

	int nGames = simulGames.intValue();
	double capRequired = 1.0/(nGames <= 0 ? 1 : nGames);

	for(Iterator I = open.iterator();I.hasNext();)
	    {
		TimeBlock tb = (TimeBlock) I.next();
		if(tb == null) continue;
		
		if(tb.capacity >= capRequired)
		    if(tb.duration() >= duration)
			{
			    return true;
			}
	    }
	return false;			
    }

    void addAvailability(TimeBlock tb)
    {
	System.out.println("Adding timeBlock open.size = " + open.size());	
	open.add(tb);
	System.out.println("Adding timeBlock open.size = " + open.size());	
    }

    void scheduleGame(Game g, int duration,Integer simulGames, AutoScheduleDoc doc)
    {
	// Find the time block
	int nGames = simulGames.intValue();
	double capRequired = 1.0/(nGames <= 0 ? 1 : nGames);
	
	for(Iterator I = open.iterator();I.hasNext();)
	    {
		TimeBlock tb = (TimeBlock) I.next();
		System.out.println("tb = "+tb);
		if(tb == null) continue;
		
		if(tb.capacity >= capRequired)
		    if(tb.duration() >= duration)
			{
			    // here is what we return
			    g.schedule(this.date,new Time(tb.a));

			    // Now, maintain the list of blocks
			    // First, if we do not use the whole block, split it
			    if(duration < tb.duration()+MIN_DURATION)
				{
				    TimeBlock remainder = new TimeBlock(tb,null);
				    remainder.a = tb.a.increment(duration);
				    tb.b = tb.a.increment(duration);
				    if(remainder.duration() > MIN_DURATION)
					{
					    int pos = open.indexOf(tb);
					    open.add(pos+1,remainder);
					}
				}
			    tb.capacity -= capRequired;
			    if(tb.capacity < MIN_CAP || tb.duration() < MIN_DURATION)
				open.remove(tb);

			    TimeBlock gb = new TimeBlock(tb,g);
			    gb.capacity = capRequired;
			    games.add(gb);
			    break;
			}
	    }
    }

    public void logAvailability(AutoScheduleDoc doc)
    {
	doc.appendToLog("Availability for day " + date);

	for(Iterator I = open.iterator();I.hasNext();)
	    {
		TimeBlock tb = (TimeBlock) I.next();
		if(tb == null) continue;
		doc.appendToLog(tb.a.toString() + " to " + tb.b.toString() + " cap = " + tb.capacity);
	    }
    }

    public String toString(){ return Local.format(date);}

    boolean hasCapacity()
    {
	return (open.size() > 0 && open.getFirst() != null);
    }


    public int compareTo(Object o)
    {
	int ret = date.compareTo(((Day) o).date);
	if(ret == 0)
	    {
		ret = start.compareTo(((Day) o).start);
		if(ret == 0)
		    {
			ret = end.compareTo(((Day) o).end);
		    }
	    }
	return ret;
    }

    static final long serialVersionUID = -7183156667945456318L;

}
